package cz.cvut.manatee.processManager;

import java.io.IOException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;
import java.util.ListIterator;


public class ProcessManager {
	
	private final LinkedList<ThreadListItem> seznamVlaken;
	private Assigner assigner = null;
	private Thread assignerThread = null;
	
	protected final QueryQueue queue;

	protected static boolean restart = false;
	protected static boolean enableProcessManager = true;
	
	public void addThreads(int pocetVlaken){
		if(pocetVlaken>0){
			//tvorba a spousteni vlaken
			for(int i=0; i<pocetVlaken; i++){
				this.addThread();
			}
		}
	}
	
	public void addThreads(){
		int novyPocet = (int)(0.2 * this.seznamVlaken.size());
		
		if(novyPocet > this.seznamVlaken.size()) this.addThreads(novyPocet);
		else this.addThreads(2);
	}
	
	public void addThread(){
		ThreadRunnable vlakno = new ThreadRunnable(this.assigner);
		Thread thread = new Thread(vlakno);
		vlakno.setThread(thread); // nastavime instanci vlakna pristup k jeho Threadu
		
		// TODO tady by to chtelo zajistit, aby ani jedna polozka nemohla byt null (asi vyhazovat vyjimku)
		this.seznamVlaken.add(new ThreadListItem(vlakno, thread)); //pridame novou dvojici do seznamu
		
		thread.start();
	}

	public synchronized void disableProcessManager() {
		Thread.currentThread().interrupt();
	}

	/*
	 * Nasilne ukonceni (prerusenim) vsech vlaken zpracovavajicich dotazy
	 */
	protected void closeAllThreads() {
		Thread thread = null;
		ThreadRunnable runnable = null;
		
		Logging.get().info("Ukoncuji vsechna vlakna");
		
		while(!this.seznamVlaken.isEmpty()){
			runnable = this.seznamVlaken.getFirst().getRunnable();
			thread = this.seznamVlaken.getFirst().getThread();
			
			if (thread.isAlive()) {
				thread.interrupt();
				try {
					thread.join();
				} catch (InterruptedException ie) {
					Logging.get().severe("PM byl neocekavane prerusen pro cekani na dokonceni vlakna!" + ie);
				}
				
				Logging.get().info("Ukonceno vlakno "+this.seznamVlaken.size()+" "+runnable);
			} else {
				Logging.get().info("Vlakno nebezelo "+this.seznamVlaken.size()+" "+runnable);
			}
			
			this.seznamVlaken.removeFirst();
			
		}
		
		Logging.get().info("Vsechna vlakna ukoncena");
	}
	
	
	protected void checkAssigner() throws InterruptedException {
		if (!assignerThread.isAlive()) {
			this.assignerStopAndCreate();
			Logging.get().severe("Byl znovu vytvoren Assigner, jako nahrada za nefunkcni.");
		}
	}
	
	
	protected void checkAllThreads() {
		int create = 0;
		
		synchronized (this.seznamVlaken) {
			ListIterator<ThreadListItem> iter = this.seznamVlaken.listIterator();
			// spocitame a smazeme vsechna neaktivni vlakna
			while (iter.hasNext()) {
				ThreadListItem item = iter.next();
				
				if (!item.getThread().isAlive()) {
					iter.remove();
					create++;
					Logging.get().severe("Nalezeno nefunkcni vlakno: " + item);
				}
			}
		}
		
		if (create > 0) {
			addThreads(create);
			Logging.get().severe("Bylo znovu vytvoreno " + create + " vlaken, jako nahrada za nefukcni.");
		}
	}

	/*
	 * metoda ktera vezme vsechny vlakna ze sveho seznamu a postupne je ukonci
	 */
	protected void closeAssigner(){
		if (this.assignerThread!=null) {
			Logging.get().info("Posilam signal ukonceni assigneru");
			
			this.assignerThread.interrupt();
			// TODO: vyresit pokud by vlakno nekomunikovalo
			try {
				this.assignerThread.join();
			} catch (InterruptedException ie) {
				// TODO zjistit jak funguji Preruseni
				Logging.get().severe("PM byl neocekavane prerusen pri cekani na Assignera!" + ie);
			}
			
			Logging.get().info("Assigner ukoncen");
		} else {
			Logging.get().warning("AssignerThread je null");
		}
		
	}
	
	public ProcessManager(QueryQueue queue) {
		//pole s vlakny a pole pro jejich spousteci umisteni
		this.seznamVlaken = new LinkedList<ThreadListItem>();
		this.queue = queue;
	}
	
	
	public void assignerStopAndCreate() throws InterruptedException {
		if (this.assigner != null) {
			this.assignerThread.interrupt();
			this.assignerThread.join();
		}
		
		this.assigner = new Assigner(queue, this.seznamVlaken);
		QueryRMIImplement.setAssigner(assigner);
		
		for (ThreadListItem item : this.seznamVlaken) {
			item.getRunnable().setAssigner(this.assigner);
		}
		
		this.assignerThread = new Thread(this.assigner);
		this.assignerThread.start();
	}
	
	
	public void run(int pocetVlaken, int timeout) {
		try{
			this.assignerStopAndCreate();
			this.addThreads(pocetVlaken);

			while(true){
				// prerusuje interrupt
				
				synchronized (this) {
					this.wait(timeout);
				}
				
				this.checkAssigner(); // overi assignera a pokud nebezi, potom ho nastartuje
				this.checkAllThreads(); // overi vypocetni vlakna a pokud nejaka nebezi, tak spusti nova
			}
		}
		catch (InterruptedException e) {
			// ukonceno na popud RMI maintance
			Logging.get().info("Assigner prijal signal pro ukonceni.");
		}
		finally {
			this.closeAssigner(); // ukonci pridelovaci proces a pocka na jeho dokonceni
			this.closeAllThreads(); // ukonci postupne vsechna vlakna
		}
	}

	
	
	
	
	
	
	
	
	
	
	public static void main(String[] args){
		// defaultni hodnoty pro process manager - budou prepsany hodnotami z configu, pokud existuji
		int pocetVlaken              = 3;
		int rmi_timeout              = 60000;
		int maintenance_timeout      = 15000;
		String queryServerName       = "ManateeQuery";
		String maintananceServerName = "ManateeMaintanance";
		

		if(args.length < 2){
			System.err.println("Nebyly zadany vstupni parametry: \"konfiguracni XML soubor\" \"adresar pro log soubor\"");
			return;
		}

		
		// vytvoreni LOG souboru
		try {
			Logging.create(args[1]); // "/tmp/"
		} catch (Exception e) {
			System.err.println("Nepodarilo se vytvorit LOG soubor.");
			System.err.println(e);
			return;
		}
		
		
		// nacteni konfiguracniho souboru
		try {
			Config config = new Config(args[0]);
			pocetVlaken           = config.getInt   ("threads.count"            , pocetVlaken);
			rmi_timeout           = config.getInt   ("rmi.timeout"              , rmi_timeout);
			maintenance_timeout   = config.getInt   ("maintenance.timeout"      , maintenance_timeout);
			queryServerName       = config.getString("rmi.queryServerName"      , queryServerName);
			maintananceServerName = config.getString("rmi.maintananceServerName", queryServerName);
			// TODO test na mezni hodnoty (min, max)
		} catch (IOException e) {
			Logging.get().severe("Nepodarilo se nacist konfiguracni soubor.:" + e);
			return;
		}
		
		Logging.get().info("Spoustim PM, bude obsahovat " + pocetVlaken + " vlaken.");
		
		//registrace RMI pro klienty
		try {
			QueryRMIImplement.setTimeout(rmi_timeout);
	        QueryRMIImplement server = new QueryRMIImplement();
	        QueryRMI stub = (QueryRMI) UnicastRemoteObject.exportObject(server, 0);
	        
	        Registry registry = LocateRegistry.getRegistry();
	        registry.rebind(queryServerName, stub);
		} catch (Exception e) {
			Logging.get().severe("Selhala registrace RMI.");
            e.printStackTrace();
            return;
        }
		
		
		
		do {
			ProcessManager.restart = false;
			OptimizedCorpus.reset(); // mozna strcime do assigneru
			OptimizedConcordance.reset();
			
			QueryQueue queue = new QueryQueue();
			ProcessManager pm = new ProcessManager(queue);
			
			pm.run(pocetVlaken, maintenance_timeout);
		} while (ProcessManager.restart);

		
		Logging.get().info("PM konci.");
	}
	


}
